package plc

import (
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"
)

// ValidType 验证类型
func ValidType(typ string) bool {
	if typ == "BOOL" || typ == "U8" || typ == "I16" || typ == "U16" || typ == "I32" || typ == "U32" || typ == "FLOAT" {
		return true
	}
	return false
}

// GetFType 转换完整类型
func GetFType(tp string) string {
	switch tp {
	case "B":
		return "U8"
	case "W":
		return "U16"
	case "I":
		return "I16"
	case "D":
		return "U32"
	case "X":
		return "BOOL"
	case "R":
		return "FLOAT"
	default:
		return "FLOAT"
	}
}

// ParseTag 解析标签代码
func ParseTag(code string, typ string) (tag *Tag, err error) {
	code = strings.ToUpper(code)              //upper
	code = strings.Replace(code, " ", "", -1) //remove spaces
	if code == "" || len(code) < 2 {
		err = fmt.Errorf("input code is invalid, code should be S7 syntax")
		return
	}
	typ = strings.ToUpper(typ)
	tag = &Tag{}
	tag.Typ = typ
	if !ValidType(typ) {
		err = fmt.Errorf("input type is invalid, type should be S7 syntax")
		return
	}
	tag.Code = code
	if code[0] == 'D' && code[1] == 'B' {
		tag.Addr = "DB"
		re := regexp.MustCompile(`^DB(\d+)\.(?:DB([BWIDXR])?)?(\d+)(?:\.([0-7]))?$`)
		match := re.FindStringSubmatch(code)
		if len(match) == 5 {
			if tag.Typ == "" {
				tag.Typ = GetFType(match[2])
			}
			tag.DbNo, _ = strconv.Atoi(match[1])
			tag.Start, _ = strconv.Atoi(match[3])
			tag.MBit, _ = strconv.Atoi(match[4])
		} else {
			err = fmt.Errorf("input code is absent, code should be S7 syntax")
			return
		}
	} else {
		re := regexp.MustCompile(`^([QIMV])([BWIDXR])?(\d+)(?:\.([0-7]))?$`)
		match := re.FindStringSubmatch(code)
		if len(match) == 5 {
			tag.Addr = match[1]
			if tag.Typ == "" {
				tag.Typ = GetFType(match[2])
			}
			tag.Start, _ = strconv.Atoi(match[3])
			tag.MBit, _ = strconv.Atoi(match[4])
		} else {
			err = fmt.Errorf("input code is absent, code should be S7 syntax")
			return
		}
	}
	switch tag.Typ {
	case "BOOL", "U8":
		tag.Size = 1
		break
	case "U16", "I16":
		tag.Size = 2
		break
	case "U32", "I32", "FLOAT":
		tag.Size = 4
		break
	default:
		tag.Size = 1
		break
	}
	return
}

// ReadTag 读取节点数据
func ReadTag(buffer []byte, offset int, tag *Tag) (value any, err error) {
	buf := buffer[offset:]
	switch tag.Typ {
	case "BOOL":
		if tag.MBit > 7 || tag.MBit < 0 {
			err = fmt.Errorf("Db read bit is invalid")
			return
		}
		mask := []byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}
		value = (buf[0] & mask[tag.MBit]) != 0
		return
	case "U8":
		value = buf[0]
		return
	case "I16":
		value = int16(binary.BigEndian.Uint16(buf[0:]))
		return
	case "U16":
		value = binary.BigEndian.Uint16(buf[0:])
		return
	case "I32":
		value = int32(binary.BigEndian.Uint32(buf[0:]))
		return
	case "U32":
		value = binary.BigEndian.Uint32(buf[0:])
		return
	case "FLOAT":
		value = float64(math.Float32frombits(binary.BigEndian.Uint32(buf[0:])))
		return
	}
	return nil, errors.New(tag.Code + "数据类型无效")
}
